Skip to content

stat_replication: fix "slot_name does not exist" by joining pg_replication_slots#1313

Open
megative wants to merge 1 commit into
prometheus-community:masterfrom
megative:fix/stat-replication-slot-name
Open

stat_replication: fix "slot_name does not exist" by joining pg_replication_slots#1313
megative wants to merge 1 commit into
prometheus-community:masterfrom
megative:fix/stat-replication-slot-name

Conversation

@megative

Copy link
Copy Markdown

Summary

Fixes #1310. The stat_replication collector currently fails every scrape with pq: column "slot_name" does not exist — affects every user on main after #1306, not just Aurora (despite the issue title).

The fix is a LEFT JOIN pg_replication_slots s ON s.active_pid = pg_stat_replication.pid so that slot_name is read from the view where it actually lives, with an empty label when no named slot is in use.

Test plan

  • go build ./..., go vet ./..., go test ./... clean.
  • Live: vanilla PG 17.10.0 — pg_scrape_collector_success{collector="stat_replication"} = 1 (was 0).
  • Live: Aurora PG 17.7.0 — pg_scrape_collector_success{collector="stat_replication"} = 1 (was 0).
Full timeline and root cause

Timeline

  1. Before Refactor stat_replication into standalone collector #1306, pg_stat_replication was scraped through the legacy yaml-driven exporter with a SELECT * FROM pg_stat_replication query. The yaml mapping listed slot_name as a label, but the column does not actually exist on pg_stat_replication — so the yaml exporter silently produced an empty label. No error.

  2. slot_name has never been a column of pg_stat_replication on any supported PG version. It lives on pg_replication_slots. To populate it for an active WAL sender you have to JOIN the two views. The legacy yaml never did. The label was effectively aspirational — described, but always empty. A latent bug sat in queries.yaml for years.

  3. Refactor stat_replication into standalone collector #1306 ("Refactor stat_replication into standalone collector") migrated this query into a Go-native collector. The new SQL was rewritten with an explicit column list, including slot_name:

    SELECT application_name, client_addr::text, state, slot_name, ...
    FROM pg_stat_replication

    That promoted slot_name from "yaml label silently missing" to "explicit SQL column that must exist". It does not.

  4. CI on Refactor stat_replication into standalone collector #1306 went green because unit tests for stat_replication use sqlmock, which returns whatever columns the test declares — the real Postgres schema is never validated. Integration tests apparently do not exercise this collector. The bug shipped.

  5. After Refactor stat_replication into standalone collector #1306 merged, stat_replication fails on every scrape, on every PG version. Because it is defaultEnabled, every user is affected automatically.

Verification that slot_name is not on pg_stat_replication

Three independent sources:

  1. \d pg_stat_replication on postgres:17.10.0 Docker — 20 columns, no slot_name.
  2. \d pg_stat_replication on postgres:18 Docker — 20 columns, no slot_name.
  3. Official docs for PG 17 and PG 18 — 20 columns documented, no slot_name. The PG 18 docs explicitly note slot_name lives on pg_stat_replication_slots.

Fix details

LEFT JOIN pg_replication_slots s ON s.active_pid = pg_stat_replication.pid and read s.slot_name. Behavior:

  • Replication client using a named slot → slot_name populated.
  • Replication client without a slot → slot_name empty (matches legacy yaml behavior).
  • Aurora and other setups without physical replication slots → slot_name empty.

Applied to both the PG ≥ 10 and pre-10 query paths. pg_replication_slots exists since PG 9.4, so the pre-9.4 branch remains affected by the same root cause; that branch is not in CI scope and was already broken before this PR.

@ArthurSens ArthurSens left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the fix, and apologies for not seeing this PR before making the 0.20.0 release.

There's still some small work to be done to make sure this also works with older versions of PG.

Could you also rebase the PR?

s.slot_name,
(case pg_is_in_recovery() when 't' then pg_xlog_location_diff(pg_last_xlog_receive_location(), replay_location)::float else pg_xlog_location_diff(pg_current_xlog_location(), replay_location)::float end) AS pg_xlog_location_diff
FROM pg_stat_replication
LEFT JOIN pg_replication_slots s ON s.active_pid = pg_stat_replication.pid

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this query only works for postgres 9.5 and above, because active_pid was only added in PG 9.5.x. See https://www.postgresql.org/docs/9.5/view-pg-replication-slots.html and https://www.postgresql.org/docs/9.4/catalog-pg-replication-slots.html

Could you please have a query for 9.4 and below that doesn't include slot_name?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ArthurSens !

Rebased on master and added a fallback for 9.2–9.4: those versions use a query without the join, slot_name label is just empty there (same as the old yaml behavior).

Verified against real postgres in docker. On 9.4 the join query indeed fails:

ERROR:  column s.active_pid does not exist

the fallback query runs fine and the collector succeeds:

pg_scrape_collector_success{collector="stat_replication"} 1

On 17 with a standby streaming through a named slot (pg_basebackup -R -S test_slot) the exporter emits:

pg_stat_replication_pg_current_wal_lsn_bytes{application_name="walreceiver",client_addr="172.19.0.3/32",slot_name="test_slot",state="streaming"} 1.1758988e+08
pg_stat_replication_pg_wal_lsn_diff{application_name="walreceiver",client_addr="172.19.0.3/32",slot_name="test_slot",state="streaming"} 0

9.6 with the join query works too. Unit tests pass, incl. a new one for the pre-9.5 path.

@megative megative force-pushed the fix/stat-replication-slot-name branch from 73c7017 to 26cafb8 Compare July 6, 2026 21:31
megative added a commit to megative/postgres_exporter that referenced this pull request Jul 6, 2026
pg_replication_slots.active_pid was only added in PostgreSQL 9.5, so the
LEFT JOIN used to resolve slot_name fails on 9.2-9.4 (verified against
postgres:9.4 in Docker: ERROR: column s.active_pid does not exist). On
those versions query pg_stat_replication directly and leave the
slot_name label empty, matching the legacy yaml exporter behavior.

Verified against live servers: fallback query runs clean on 9.4, the
active_pid JOIN works on 9.6, and on 17 with a streaming standby using a
named slot the exporter emits pg_stat_replication_* metrics with
slot_name populated.

Addresses review feedback on prometheus-community#1313.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@megative megative requested a review from ArthurSens July 6, 2026 21:32
@megative megative force-pushed the fix/stat-replication-slot-name branch from 26cafb8 to 9ba03e5 Compare July 6, 2026 21:33
…ation_slots

The stat_replication collector currently fails every scrape with:

  pq: column "slot_name" does not exist

slot_name is not a column of pg_stat_replication on any supported PostgreSQL version (verified on PG 17.10.0 Docker, PG 18 Docker, and the official PG 17 and PG 18 docs). It lives on pg_replication_slots and has to be pulled in via a JOIN on active_pid = pid.

Before prometheus-community#1306 the same collector ran via the legacy yaml-driven exporter, which used SELECT * and silently produced an empty slot_name label when the column was missing. prometheus-community#1306 promoted slot_name into an explicit SQL column list, turning that silent no-op into a hard parse-time error. CI did not catch it because the unit tests use sqlmock and do not validate against the real Postgres schema.

stat_replication is defaultEnabled, so every user on main after prometheus-community#1306 sees the collector dead on every scrape, regardless of whether they run Aurora or vanilla Postgres.

Fix: LEFT JOIN pg_replication_slots s ON s.active_pid = pg_stat_replication.pid and read s.slot_name. The label is populated when a named slot is in use, empty otherwise (matching the previous yaml behavior).

Since pg_replication_slots.active_pid only exists on PostgreSQL 9.5+, versions 9.2-9.4 fall back to a query without the join and leave the slot_name label empty, matching the legacy yaml behavior. Verified against live postgres:9.4, 9.6 and 17 (with a streaming standby on a named slot) in Docker.

Fixes prometheus-community#1310.

Signed-off-by: Pavel K <megativ3@gmail.com>
@megative megative force-pushed the fix/stat-replication-slot-name branch from 9ba03e5 to 7a3b6c1 Compare July 6, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: stat_replication collector crashes ("slot_name" does not exist)

2 participants